In this analysis module, we have annotated cell types in
SCPCP000004 samples using two methods: SingleR
and scANVI/scArches, both using the NBAtlas dataset as a
reference. In this notebook, we derive the final annotations using
information from the SingleR labels, the
scANVI/scArches labels, and existing consensus cell type
annotations. All final annotations use labels present in the NBAtlas
dataset.
We use the following approach to determine the final annotation.
Throughout, we use CL ontology ids to perform comparisons. For more
information about how these ontology ids were determined for NBAtlas
labels, see ../references/README.md.
- If
SingleR and scANVI/scArches labels
exactly agree, we assign that label
- If broad label families (e.g.,
T cell would be the
broad family for CD4+ T cell) for SingleR and
scANVI/scArches labels agree, we assign the broad family
label.
- If
SingleR and scANVI/scArches labels
disagree, we then compare to the consensus cell type; if at least one
inference agrees with the consensus cell type, we assign the inferred
label.
- We perform this first at the label level and then at the broad
family level.
- Finally, following the conclusions of Bonine et al. (2024), we
designate any cell labeled as
Neuroendocrine as a tumor
cell.
Setup
suppressPackageStartupMessages({
library(ggplot2)
library(patchwork)
library(SingleCellExperiment)
})
theme_set(
theme_bw()
)
umap_theme <- list(
theme(
axis.text = element_blank(),
axis.ticks = element_blank(),
legend.position = "bottom",
aspect.ratio = 1
)
)
# settings
options(
dplyr.summarise.inform = FALSE,
readr.show_col_types = FALSE
)
Paths
module_dir <- rprojroot::find_root(rprojroot::is_renv_project)
repository_base <- file.path(module_dir, "..", "..")
data_dir <- file.path(repository_base, "data", "current", "SCPCP000004")
merged_dir <- file.path(repository_base, "data", "current", "results", "merge-sce", "SCPCP000004")
ref_dir <- file.path(module_dir, "references")
results_dir <- file.path(module_dir, "results")
singler_dir <- file.path(results_dir, "singler")
scanvi_dir <- file.path(results_dir, "scanvi")
# merged SCE file
sce_file <- file.path(
merged_dir,
"SCPCP000004_merged.rds"
)
# SingleR files
singler_files <- list.files(
path = singler_dir,
pattern = "_singler-annotations\\.tsv",
recursive = TRUE,
full.names = TRUE
) |>
# add names as library id
purrr::set_names(
\(x) {
stringr::str_remove(basename(x), "_singler-annotations.tsv")
}
)
# scanvi predictions
scanvi_file <- file.path(
scanvi_dir,
"scanvi-predictions.tsv"
)
# palette file
palette_file <- file.path(
module_dir,
"palettes",
"nbatlas-cell-type-palette.tsv"
)
# label map file
nbatlas_label_map_file <- file.path(
ref_dir,
"nbatlas-label-map.tsv"
)
# label ontologies file
nbatlas_ontology_file <- file.path(
ref_dir,
"nbatlas-ontology-ids.tsv"
)
# marker genes from NBAtlas
nbatlas_markers_file <- file.path(
ref_dir,
"nbatlas-marker-genes.tsv"
)
# validation groups URL, used to obtain consensus "family" ontologies (aka broad validation group ontologies)
validation_url <- "https://raw.githubusercontent.com/AlexsLemonade/OpenScPCA-analysis/refs/heads/main/analyses/cell-type-consensus/references/consensus-validation-groups.tsv"
Functions
# Source utilities functions:
# - subset_nbatlas_markers()
# - faceted_umap()
# - generate_dotplot()
source(file.path(module_dir, "scripts", "utils", "celltype-utils.R"))
#' Prepare data frame of scANVI or SingleR labels for annotation
#'
#' @param df Data frame to prepare
#' @param annot_type "singler" or "scanvi"
#' @param ontology_df Data frame of ontology ids
#' @param label_map_df Data frame mapping labels to family labels
#'
#' @returns Wide data frame with ontologies and family labels for the given annotation type
prep_for_annotation <- function(
df,
annot_type,
ontology_df,
label_map_df) {
df |>
dplyr::select(all_of(c("cell_id", annot_type))) |>
dplyr::rename(label = annot_type) |>
####### Join in the family labels
dplyr::left_join(label_map_df, by = c("label" = "NBAtlas_label")) |>
dplyr::rename(family = NBAtlas_family) |>
######### Obtain LABEL ontologies
dplyr::left_join(ontology_slim_df, by = c("label" = "NBAtlas_label")) |>
dplyr::rename(label_ontology = CL_ontology_id) |>
######## Obtain FAMILY ontologies
dplyr::left_join(ontology_slim_df, by = c("family" = "NBAtlas_label")) |>
dplyr::rename(family_ontology = CL_ontology_id) |>
# rename columns to start with `annot_type`
dplyr::rename_with(\(x) {paste(annot_type, x, sep = "_")}, -cell_id)
}
Read and prepare input data
Read merged SCE object:
merged_sce <- readRDS(sce_file)
# immediately remove assays we don't need for space
assay(merged_sce, "spliced") <- NULL
assay(merged_sce, "counts") <- NULL
reducedDim(merged_sce, "PCA") <- NULL
Read SingleR results:
singler_df <- singler_files |>
purrr::map(readr::read_tsv) |>
purrr::list_rbind(names_to = "library_id") |>
dplyr::mutate(
# add cell id column for unique rows & joining
cell_id = glue::glue("{library_id}-{barcodes}"),
# recode NA -> "Unknown" and NE -> "Neuroendocrine"
singler = dplyr::case_when(
is.na(pruned.labels) ~ "Unknown",
pruned.labels == "NE" ~ "Neuroendocrine",
.default = pruned.labels)
) |>
dplyr::select(cell_id, singler)
Read scANVI results:
scanvi_df <- readr::read_tsv(
file.path(scanvi_dir, "scanvi_predictions.tsv")
) |>
dplyr::select(
cell_id,
scanvi = scanvi_prediction,
starts_with("pp_")
) |>
tidyr::pivot_longer(
starts_with("pp_"),
names_to = "posterior_celltype",
values_to = "posterior"
) |>
dplyr::mutate(posterior_celltype = stringr::str_remove(posterior_celltype, "^pp_")) |>
dplyr::filter(scanvi == posterior_celltype) |>
# recode to Unknown if below the threshold, and NE -> Neuroendocrine
dplyr::mutate(scanvi = dplyr::case_when(
posterior < params$scanvi_pp_threshold ~ "Unknown",
scanvi == "NE" ~ "Neuroendocrine",
.default = scanvi)) |>
dplyr::select(cell_id, scanvi)
Read additional helper files:
palette_df <- readr::read_tsv(palette_file)
celltype_pal <- palette_df$hex_color
names(celltype_pal) <- palette_df$NBAtlas_label
label_map_df <- readr::read_tsv(nbatlas_label_map_file)
ontology_df <- readr::read_tsv(nbatlas_ontology_file)
nbatlas_markers_df <- readr::read_tsv(nbatlas_markers_file)
validation_df <- readr::read_tsv(validation_url) |>
dplyr::rename(
consensus = consensus_annotation,
consensus_family_label = validation_group_annotation,
consensus_family_ontology = validation_group_ontology
)
Combine annotations into a single data frame:
celltype_df <- scuttle::makePerCellDF(
merged_sce,
use.coldata = c("sample_id", "is_xenograft", "consensus_celltype_annotation", "consensus_celltype_ontology")
) |>
tibble::rownames_to_column("cell_id") |>
tidyr::separate(cell_id, into = c("library_id", "cell_barcode"), remove = FALSE) |>
# keep UMAP for eventual viz
dplyr::rename(
consensus = consensus_celltype_annotation,
consensus_ontology = consensus_celltype_ontology,
UMAP1 = UMAP.1,
UMAP2 = UMAP.2
) |>
dplyr::left_join(singler_df, by = "cell_id") |>
dplyr::left_join(scanvi_df, by = "cell_id")
# Pull out metadata we'll want later
sample_metadata <- celltype_df |>
dplyr::select(sample_id, library_id, is_xenograft) |>
unique()
Annotate
Firt we prepare the data frame for annotation.
ontology_slim_df <- ontology_df |>
dplyr::select(-CL_annotation)
singler_annotation_df <- prep_for_annotation(
celltype_df,
"singler",
ontology_slim_df,
label_map_df
)
scanvi_annotation_df <- prep_for_annotation(
celltype_df,
"scanvi",
ontology_slim_df,
label_map_df
)
annotation_df <- celltype_df |>
dplyr::select(cell_id, consensus, consensus_ontology) |>
dplyr::left_join(singler_annotation_df, by = "cell_id") |>
dplyr::left_join(scanvi_annotation_df, by = "cell_id") |>
dplyr::left_join(validation_df, by = c("consensus", "consensus_ontology")) |>
dplyr::select(cell_id, starts_with("consensus"), starts_with("singler"), starts_with("scanvi"))
In the code below, we compare ontology ids to derive final
annotations. However, based on those comparisons, we assign a final
label, not final ontology id, and then add the final ontology
ids back into those annotations. This is because we have several
NA ontology ids, and we would not be able to unambiguously
map their labels in if we assigned ontologies.
final_annotation_df <- annotation_df |>
dplyr::mutate(
# For all comparisons, make sure we aren't comparing NA to NA and getting TRUE
final_label = dplyr::case_when(
########### Check for exact match between SingleR/scANVI
!is.na(scanvi_label_ontology) & singler_label_ontology == scanvi_label_ontology ~ scanvi_label,
########### Check for family match between SingleR/scANVI
!is.na(scanvi_family_ontology) & singler_family_ontology == scanvi_family_ontology ~ scanvi_family,
########## Now use agreement with consensus to assign a label: first by label, and then by family
!is.na(consensus_ontology) & singler_label_ontology == consensus_ontology ~ singler_label,
!is.na(consensus_ontology) & scanvi_label_ontology == consensus_ontology ~ scanvi_label,
!is.na(consensus_family_ontology) & singler_family_ontology == consensus_family_ontology ~ singler_family,
!is.na(consensus_family_ontology) & scanvi_family_ontology == consensus_family_ontology ~ scanvi_family,
# Everything else is Unknown
.default = "Unknown"
)
) |>
# Now, we can bring map the final ontologies into the data frame
dplyr::inner_join(
ontology_df |> dplyr::rename(final_label = NBAtlas_label, final_id = CL_ontology_id),
by = "final_label"
) |>
# add tumor column for visualization
dplyr::mutate(cell_class = dplyr::case_when(
final_label == "Neuroendocrine" ~ "tumor",
final_label == "Unknown" ~ "unknown",
.default = "normal"
))
Explore final annotations
What fraction of cells have been annotated?
sum(!is.na(final_annotation_df$final_id)) / nrow(final_annotation_df)
[1] 0.8506399
What fraction of cells have been annotated per library?
frac_labeled_df <- final_annotation_df |>
tidyr::separate(cell_id, into = c("library_id", "barcode"), remove = FALSE) |>
dplyr::group_by(library_id) |>
dplyr::summarize(
frac_labeled = sum(!is.na(final_id))/dplyr::n(),
library_size = dplyr::n()
) |>
# get PDX logical for coloring
dplyr::inner_join(sample_metadata, by = "library_id")
p1 <- ggplot(frac_labeled_df) +
aes(x = frac_labeled) +
geom_histogram(bins = 40) +
labs(x = "Fraction of library labeled") +
theme_bw()
p2 <- ggplot(frac_labeled_df) +
aes(x = library_size, y = frac_labeled, color = is_xenograft) +
geom_point() +
labs(
x = "Total cells in library",
y = "Fraction of library labeled"
) +
theme_bw() +
theme(legend.position = "bottom")
p1 + p2

UMAPs
In this section, we show the UMAP from the merged (not
batch-corrected!) SCPCP000004 object colored by cell type
annotations and other potentially relevant metadata.
label_order <- label_map_df |>
dplyr::add_count(NBAtlas_family) |>
dplyr::arrange(desc(n), NBAtlas_family) |>
dplyr::filter(NBAtlas_label %in% final_annotation_df$final_label) |>
dplyr::pull(NBAtlas_label)
label_order <- c(label_order, "Unknown")
# prepare data frame for plotting
umap_df <- celltype_df |>
dplyr::select(cell_id, sample_id, library_id, is_xenograft, UMAP1, UMAP2) |>
dplyr::left_join(final_annotation_df, by = "cell_id") |>
dplyr::mutate(final_label = factor(final_label, levels = label_order))
Colored by cell type assignment
Note that in the UMAP below, cell types in these same “family” each
share a color:
- T cells
- Natural killer cells
- Conventional dendritic cells
- Monocytes
ggplot(umap_df) +
aes(x = UMAP1, y = UMAP2, color = final_label) +
geom_point(alpha = 0.2, size = 0.3) +
scale_color_manual(values = celltype_pal) +
guides(color = guide_legend(override.aes = list(size = 2, alpha = 1))) +
umap_theme +
theme(legend.position = "bottom")

Faceted by cell type assignment
This UMAP is the same as the previous one, except each facet
highlights one specific cell type. In this plot, only cell types with at
least 10 cells are shown.
umap_df |>
dplyr::add_count(final_label) |>
dplyr::filter(n > 10) |>
faceted_umap(
annotation_column = final_label,
celltype_colors = celltype_pal,
facet_wrap_cols = 6
) +
umap_theme +
theme(legend.position = "none")

Colored by tumor classification
ggplot(umap_df) +
aes(x = UMAP1, y = UMAP2, color = cell_class) +
geom_point(alpha = 0.2, size = 0.3) +
scale_color_brewer(palette = "Set2") +
guides(color = guide_legend(override.aes = list(size = 2, alpha = 1))) +
umap_theme

Colored by PDX status
ggplot(umap_df) +
aes(x = UMAP1, y = UMAP2, color = is_xenograft) +
geom_point(alpha = 0.2, size = 0.3) +
scale_color_brewer(palette = "Set1") +
guides(color = guide_legend(override.aes = list(size = 2, alpha = 1))) +
umap_theme

Dot plot
This section shows dot plots of marker gene expression for the final
annotations. Specifically, we show the top 5 highest log2FC
marker genes per NBAtlas cell type for validation. Marker genes are
taken directly from the NBAtlas paper where they were identified with
Seurat::FindMarkers().
Marker genes that are present in three or more cell types are not
included. In addition, the upper limit for the color scale has been
censored to a maximum of 6 to ensure visibility of expression levels
across all cell types.
input_dotplot_df <- final_annotation_df |>
dplyr::filter(final_label != "Unknown") |>
# recode cell types to match what is present in NBAtlas marker genes
dplyr::mutate(
label_recoded = dplyr::case_when(
stringr::str_detect(final_label, "monocyte") ~ "Monocyte",
stringr::str_detect(final_label, "cDC") ~ "cDC",
.default = final_label
)) |>
dplyr::select(cell_id, label_recoded)
# data frame of top 5 upregulated genes per cell type
top_nbatlas_markers_df <- subset_nbatlas_markers(nbatlas_markers_df, 5, "up") |>
# only markers in no more than 2 categories
# this is a good middle ground to ensure there are at least 2 markers per cell type
dplyr::add_count(ensembl_gene_id) |>
dplyr::filter(n < 3) |>
dplyr::select(-n)
# get total number of cells per final annotation group and set up y_label
total_cells_df <- input_dotplot_df |>
dplyr::count(label_recoded, name = "total_cells") |>
dplyr::mutate(y_label = glue::glue("{label_recoded} ({total_cells})")) |>
dplyr::inner_join(label_map_df, by = c("label_recoded" = "NBAtlas_label")) |>
dplyr::group_by(NBAtlas_family) |>
dplyr::mutate(family_total_cells = sum(total_cells)) |>
# order by family count, but put Unknown at the end
dplyr::arrange(desc(family_total_cells)) |>
# amazing new dplyr trick; only this row is TRUE, so it gets moved to the last row
dplyr::arrange(label_recoded == "Unknown")
total_cells_df$y_label <- factor(total_cells_df$y_label, levels = rev(total_cells_df$y_label))
nbatlas_bar_order <- total_cells_df$label_recoded
# Prepare the expressed_genes vector
# we only care about if that gene is expressed otherwise we won't waste memory and include it
gene_sums <- rowData(merged_sce) |>
as.data.frame() |>
dplyr::select(contains("detected")) |>
as.matrix() |>
rowSums()
expressed_genes <- names(gene_sums)[gene_sums > 0]
generate_dotplot(
merged_sce = merged_sce,
markers_df = top_nbatlas_markers_df,
celltype_df = input_dotplot_df,
total_cells_df = total_cells_df,
expressed_genes = expressed_genes,
bar_order = nbatlas_bar_order,
celltype_palette = celltype_pal,
min_cells = 10 # only show cell types with at least 10 cells
) +
theme(legend.position = "bottom")

Export
Export the final table with submission-ready column names.
final_annotation_df |>
dplyr::select(
cell_id,
cell_type_assignment = final_label,
tumor_cell_classification = cell_class,
CL_ontology_id = final_id,
CL_annotation
) |>
readr::write_tsv(
params$annotations_tsv
)
Session Info
sessionInfo()
R version 4.4.0 (2024-04-24)
Platform: aarch64-apple-darwin20
Running under: macOS 15.6
Matrix products: default
BLAS: /Library/Frameworks/R.framework/Versions/4.4-arm64/Resources/lib/libRblas.0.dylib
LAPACK: /Library/Frameworks/R.framework/Versions/4.4-arm64/Resources/lib/libRlapack.dylib; LAPACK version 3.12.0
locale:
[1] en_US.UTF-8/en_US.UTF-8/en_US.UTF-8/C/en_US.UTF-8/en_US.UTF-8
time zone: America/New_York
tzcode source: internal
attached base packages:
[1] stats4 stats graphics grDevices datasets utils methods
[8] base
other attached packages:
[1] SingleCellExperiment_1.26.0 SummarizedExperiment_1.34.0
[3] Biobase_2.64.0 GenomicRanges_1.56.2
[5] GenomeInfoDb_1.40.1 IRanges_2.38.1
[7] S4Vectors_0.42.1 BiocGenerics_0.50.0
[9] MatrixGenerics_1.16.0 matrixStats_1.5.0
[11] patchwork_1.3.0 ggplot2_3.5.2
loaded via a namespace (and not attached):
[1] gtable_0.3.6 xfun_0.52
[3] bslib_0.9.0 lattice_0.22-6
[5] tzdb_0.5.0 bitops_1.0-9
[7] vctrs_0.6.5 tools_4.4.0
[9] generics_0.1.4 curl_6.3.0
[11] parallel_4.4.0 tibble_3.3.0
[13] pkgconfig_2.0.3 Matrix_1.7-1
[15] RColorBrewer_1.1-3 sparseMatrixStats_1.16.0
[17] lifecycle_1.0.4 GenomeInfoDbData_1.2.12
[19] compiler_4.4.0 farver_2.1.2
[21] stringr_1.5.1 codetools_0.2-20
[23] htmltools_0.5.8.1 sass_0.4.10
[25] yaml_2.3.10 pillar_1.10.2
[27] crayon_1.5.3 jquerylib_0.1.4
[29] tidyr_1.3.1 BiocParallel_1.38.0
[31] DelayedArray_0.30.1 cachem_1.1.0
[33] abind_1.4-8 tidyselect_1.2.1
[35] digest_0.6.37 stringi_1.8.7
[37] dplyr_1.1.4 purrr_1.0.4
[39] labeling_0.4.3 rprojroot_2.0.4
[41] fastmap_1.2.0 grid_4.4.0
[43] cli_3.6.5 SparseArray_1.4.8
[45] magrittr_2.0.3 S4Arrays_1.4.1
[47] readr_2.1.5 withr_3.0.2
[49] DelayedMatrixStats_1.26.0 scales_1.4.0
[51] UCSC.utils_1.0.0 bit64_4.6.0-1
[53] rmarkdown_2.29 XVector_0.44.0
[55] httr_1.4.7 jpeg_0.1-11
[57] ggmap_4.0.1 bit_4.6.0
[59] png_0.1-8 hms_1.1.3
[61] beachmat_2.20.0 evaluate_1.0.3
[63] knitr_1.50 viridisLite_0.4.2
[65] rlang_1.1.6 Rcpp_1.0.14
[67] scuttle_1.14.0 glue_1.8.0
[69] BiocManager_1.30.26 renv_1.1.3
[71] vroom_1.6.5 jsonlite_2.0.0
[73] plyr_1.8.9 R6_2.6.1
[75] zlibbioc_1.50.0
LS0tCnRpdGxlOiAiRGVyaXZlIGZpbmFsIGFubm90YXRpb25zIGJhc2VkIG9uIE5CQXRsYXMgcmVmZXJlbmNlIgphdXRob3I6ICJTdGVwaGFuaWUgSi4gU3BpZWxtYW4iCmRhdGU6ICJgciBTeXMuRGF0ZSgpYCIKb3V0cHV0OgogIGh0bWxfbm90ZWJvb2s6CiAgICB0b2M6IHRydWUKICAgIHRvY19kZXB0aDogMwogICAgY29kZV9mb2xkaW5nOiBoaWRlCnBhcmFtczoKICBzY2FudmlfcHBfdGhyZXNob2xkOiAwLjc1ICAgIAogIGFubm90YXRpb25zX3RzdjogInJlc3VsdHMvU0NQQ1AwMDAwMDQtYW5ub3RhdGlvbnMudHN2IgogIHRlc3Rpbmc6IEZBTFNFCi0tLQoKSW4gdGhpcyBhbmFseXNpcyBtb2R1bGUsIHdlIGhhdmUgYW5ub3RhdGVkIGNlbGwgdHlwZXMgaW4gYFNDUENQMDAwMDA0YCBzYW1wbGVzIHVzaW5nIHR3byBtZXRob2RzOiBgU2luZ2xlUmAgYW5kIGBzY0FOVkkvc2NBcmNoZXNgLCBib3RoIHVzaW5nIHRoZSBOQkF0bGFzIGRhdGFzZXQgYXMgYSByZWZlcmVuY2UuCkluIHRoaXMgbm90ZWJvb2ssIHdlIGRlcml2ZSB0aGUgZmluYWwgYW5ub3RhdGlvbnMgdXNpbmcgaW5mb3JtYXRpb24gZnJvbSB0aGUgYFNpbmdsZVJgIGxhYmVscywgdGhlIGBzY0FOVkkvc2NBcmNoZXNgIGxhYmVscywgYW5kIGV4aXN0aW5nIGNvbnNlbnN1cyBjZWxsIHR5cGUgYW5ub3RhdGlvbnMuCkFsbCBmaW5hbCBhbm5vdGF0aW9ucyB1c2UgbGFiZWxzIHByZXNlbnQgaW4gdGhlIE5CQXRsYXMgZGF0YXNldC4KCldlIHVzZSB0aGUgZm9sbG93aW5nIGFwcHJvYWNoIHRvIGRldGVybWluZSB0aGUgZmluYWwgYW5ub3RhdGlvbi4KVGhyb3VnaG91dCwgd2UgdXNlIENMIG9udG9sb2d5IGlkcyB0byBwZXJmb3JtIGNvbXBhcmlzb25zLiAKRm9yIG1vcmUgaW5mb3JtYXRpb24gYWJvdXQgaG93IHRoZXNlIG9udG9sb2d5IGlkcyB3ZXJlIGRldGVybWluZWQgZm9yIE5CQXRsYXMgbGFiZWxzLCBzZWUgYC4uL3JlZmVyZW5jZXMvUkVBRE1FLm1kYC4KCjEuIElmIGBTaW5nbGVSYCBhbmQgYHNjQU5WSS9zY0FyY2hlc2AgbGFiZWxzIGV4YWN0bHkgYWdyZWUsIHdlIGFzc2lnbiB0aGF0IGxhYmVsCjIuIElmIGJyb2FkIGxhYmVsIGZhbWlsaWVzIChlLmcuLCBgVCBjZWxsYCB3b3VsZCBiZSB0aGUgYnJvYWQgZmFtaWx5IGZvciBgQ0Q0KyBUIGNlbGxgKSBmb3IgYFNpbmdsZVJgIGFuZCBgc2NBTlZJL3NjQXJjaGVzYCBsYWJlbHMgYWdyZWUsIHdlIGFzc2lnbiB0aGUgYnJvYWQgZmFtaWx5IGxhYmVsLgogICogU2VlIFtgcmVmZXJlbmNlcy9uYmF0bGFzLWxhYmVsLW1hcC50c3ZgXShyZWZlcmVuY2VzL25iYXRsYXMtbGFiZWwtbWFwLnRzdikgYW5kIFtgcmVmZXJlbmNlcy9SRUFETUUubWRgXShyZWZlcmVuY2VzL1JFQURNRS5tZCkgZm9yIGhvdyBsYWJlbHMgd2VyZSBtYXBwZWQgdG8gZmFtaWxpZXMuCjMuIElmIGBTaW5nbGVSYCBhbmQgYHNjQU5WSS9zY0FyY2hlc2AgbGFiZWxzIGRpc2FncmVlLCB3ZSB0aGVuIGNvbXBhcmUgdG8gdGhlIGNvbnNlbnN1cyBjZWxsIHR5cGU7IGlmIGF0IGxlYXN0IG9uZSBpbmZlcmVuY2UgYWdyZWVzIHdpdGggdGhlIGNvbnNlbnN1cyBjZWxsIHR5cGUsIHdlIGFzc2lnbiB0aGUgaW5mZXJyZWQgbGFiZWwuCiAgKiBXZSBwZXJmb3JtIHRoaXMgZmlyc3QgYXQgdGhlIGxhYmVsIGxldmVsIGFuZCB0aGVuIGF0IHRoZSBicm9hZCBmYW1pbHkgbGV2ZWwuCjQuIEZpbmFsbHksIGZvbGxvd2luZyB0aGUgY29uY2x1c2lvbnMgb2YgQm9uaW5lIGV0IGFsLiAoMjAyNCksIHdlIGRlc2lnbmF0ZSBhbnkgY2VsbCBsYWJlbGVkIGFzIGBOZXVyb2VuZG9jcmluZWAgYXMgYSB0dW1vciBjZWxsLgoKCiMjIFNldHVwCgpgYGB7ciwgd2FybmluZyA9IEZBTFNFfQpzdXBwcmVzc1BhY2thZ2VTdGFydHVwTWVzc2FnZXMoewogIGxpYnJhcnkoZ2dwbG90MikKICBsaWJyYXJ5KHBhdGNod29yaykKICBsaWJyYXJ5KFNpbmdsZUNlbGxFeHBlcmltZW50KQp9KQoKdGhlbWVfc2V0KAogIHRoZW1lX2J3KCkKKQp1bWFwX3RoZW1lIDwtIGxpc3QoCiAgdGhlbWUoCiAgICBheGlzLnRleHQgPSBlbGVtZW50X2JsYW5rKCksIAogICAgYXhpcy50aWNrcyA9IGVsZW1lbnRfYmxhbmsoKSwgCiAgICBsZWdlbmQucG9zaXRpb24gPSAiYm90dG9tIiwgCiAgICBhc3BlY3QucmF0aW8gPSAxCiAgKQopCgojIHNldHRpbmdzCm9wdGlvbnMoCiAgZHBseXIuc3VtbWFyaXNlLmluZm9ybSA9IEZBTFNFLCAKICByZWFkci5zaG93X2NvbF90eXBlcyA9IEZBTFNFCikKYGBgCgojIyMgUGF0aHMKCmBgYHtyIGJhc2UgcGF0aHN9Cm1vZHVsZV9kaXIgPC0gcnByb2pyb290OjpmaW5kX3Jvb3QocnByb2pyb290Ojppc19yZW52X3Byb2plY3QpCnJlcG9zaXRvcnlfYmFzZSA8LSBmaWxlLnBhdGgobW9kdWxlX2RpciwgIi4uIiwgIi4uIikKCmRhdGFfZGlyIDwtIGZpbGUucGF0aChyZXBvc2l0b3J5X2Jhc2UsICJkYXRhIiwgImN1cnJlbnQiLCAiU0NQQ1AwMDAwMDQiKQptZXJnZWRfZGlyIDwtIGZpbGUucGF0aChyZXBvc2l0b3J5X2Jhc2UsICJkYXRhIiwgImN1cnJlbnQiLCAicmVzdWx0cyIsICJtZXJnZS1zY2UiLCAiU0NQQ1AwMDAwMDQiKQpyZWZfZGlyIDwtIGZpbGUucGF0aChtb2R1bGVfZGlyLCAicmVmZXJlbmNlcyIpCnJlc3VsdHNfZGlyIDwtIGZpbGUucGF0aChtb2R1bGVfZGlyLCAicmVzdWx0cyIpCnNpbmdsZXJfZGlyIDwtIGZpbGUucGF0aChyZXN1bHRzX2RpciwgInNpbmdsZXIiKQpzY2FudmlfZGlyIDwtIGZpbGUucGF0aChyZXN1bHRzX2RpciwgInNjYW52aSIpCmBgYAoKYGBge3IgZmlsZSBwYXRoc30KIyBtZXJnZWQgU0NFIGZpbGUKc2NlX2ZpbGUgPC0gZmlsZS5wYXRoKAogIG1lcmdlZF9kaXIsCiAgIlNDUENQMDAwMDA0X21lcmdlZC5yZHMiCikKCiMgU2luZ2xlUiBmaWxlcwpzaW5nbGVyX2ZpbGVzIDwtIGxpc3QuZmlsZXMoCiAgcGF0aCA9IHNpbmdsZXJfZGlyLAogIHBhdHRlcm4gPSAiX3NpbmdsZXItYW5ub3RhdGlvbnNcXC50c3YiLAogIHJlY3Vyc2l2ZSA9IFRSVUUsCiAgZnVsbC5uYW1lcyA9IFRSVUUKKSB8PgogICMgYWRkIG5hbWVzIGFzIGxpYnJhcnkgaWQKICBwdXJycjo6c2V0X25hbWVzKAogICAgXCh4KSB7CiAgICAgIHN0cmluZ3I6OnN0cl9yZW1vdmUoYmFzZW5hbWUoeCksICJfc2luZ2xlci1hbm5vdGF0aW9ucy50c3YiKQogICAgfQogICkKCiMgc2NhbnZpIHByZWRpY3Rpb25zCnNjYW52aV9maWxlIDwtIGZpbGUucGF0aCgKICBzY2FudmlfZGlyLCAKICAic2NhbnZpLXByZWRpY3Rpb25zLnRzdiIKKQoKIyBwYWxldHRlIGZpbGUKcGFsZXR0ZV9maWxlIDwtIGZpbGUucGF0aCgKICBtb2R1bGVfZGlyLAogICJwYWxldHRlcyIsCiAgIm5iYXRsYXMtY2VsbC10eXBlLXBhbGV0dGUudHN2IgopCgojIGxhYmVsIG1hcCBmaWxlCm5iYXRsYXNfbGFiZWxfbWFwX2ZpbGUgPC0gZmlsZS5wYXRoKAogIHJlZl9kaXIsIAogICJuYmF0bGFzLWxhYmVsLW1hcC50c3YiCikKCiMgbGFiZWwgb250b2xvZ2llcyBmaWxlCm5iYXRsYXNfb250b2xvZ3lfZmlsZSA8LSBmaWxlLnBhdGgoCiAgcmVmX2RpciwgCiAgIm5iYXRsYXMtb250b2xvZ3ktaWRzLnRzdiIKKQoKIyBtYXJrZXIgZ2VuZXMgZnJvbSBOQkF0bGFzCm5iYXRsYXNfbWFya2Vyc19maWxlIDwtIGZpbGUucGF0aCgKICByZWZfZGlyLCAKICAibmJhdGxhcy1tYXJrZXItZ2VuZXMudHN2IgopCgojIHZhbGlkYXRpb24gZ3JvdXBzIFVSTCwgdXNlZCB0byBvYnRhaW4gY29uc2Vuc3VzICJmYW1pbHkiIG9udG9sb2dpZXMgKGFrYSBicm9hZCB2YWxpZGF0aW9uIGdyb3VwIG9udG9sb2dpZXMpCnZhbGlkYXRpb25fdXJsIDwtICJodHRwczovL3Jhdy5naXRodWJ1c2VyY29udGVudC5jb20vQWxleHNMZW1vbmFkZS9PcGVuU2NQQ0EtYW5hbHlzaXMvcmVmcy9oZWFkcy9tYWluL2FuYWx5c2VzL2NlbGwtdHlwZS1jb25zZW5zdXMvcmVmZXJlbmNlcy9jb25zZW5zdXMtdmFsaWRhdGlvbi1ncm91cHMudHN2IgpgYGAKCgojIyMgRnVuY3Rpb25zCgpgYGB7cn0KIyBTb3VyY2UgdXRpbGl0aWVzIGZ1bmN0aW9uczoKIyAtIHN1YnNldF9uYmF0bGFzX21hcmtlcnMoKQojIC0gZmFjZXRlZF91bWFwKCkKIyAtIGdlbmVyYXRlX2RvdHBsb3QoKQpzb3VyY2UoZmlsZS5wYXRoKG1vZHVsZV9kaXIsICJzY3JpcHRzIiwgInV0aWxzIiwgImNlbGx0eXBlLXV0aWxzLlIiKSkKYGBgCgoKYGBge3J9CgojJyBQcmVwYXJlIGRhdGEgZnJhbWUgb2Ygc2NBTlZJIG9yIFNpbmdsZVIgbGFiZWxzIGZvciBhbm5vdGF0aW9uCiMnCiMnIEBwYXJhbSBkZiBEYXRhIGZyYW1lIHRvIHByZXBhcmUKIycgQHBhcmFtIGFubm90X3R5cGUgInNpbmdsZXIiIG9yICJzY2FudmkiCiMnIEBwYXJhbSBvbnRvbG9neV9kZiBEYXRhIGZyYW1lIG9mIG9udG9sb2d5IGlkcwojJyBAcGFyYW0gbGFiZWxfbWFwX2RmIERhdGEgZnJhbWUgbWFwcGluZyBsYWJlbHMgdG8gZmFtaWx5IGxhYmVscwojJwojJyBAcmV0dXJucyBXaWRlIGRhdGEgZnJhbWUgd2l0aCBvbnRvbG9naWVzIGFuZCBmYW1pbHkgbGFiZWxzIGZvciB0aGUgZ2l2ZW4gYW5ub3RhdGlvbiB0eXBlCnByZXBfZm9yX2Fubm90YXRpb24gPC0gZnVuY3Rpb24oCiAgICBkZiwgCiAgICBhbm5vdF90eXBlLCAKICAgIG9udG9sb2d5X2RmLCAKICAgIGxhYmVsX21hcF9kZikgewoKICBkZiB8PgogICAgZHBseXI6OnNlbGVjdChhbGxfb2YoYygiY2VsbF9pZCIsIGFubm90X3R5cGUpKSkgfD4KICAgIGRwbHlyOjpyZW5hbWUobGFiZWwgPSBhbm5vdF90eXBlKSB8PgogICAgIyMjIyMjIyBKb2luIGluIHRoZSBmYW1pbHkgbGFiZWxzCiAgICBkcGx5cjo6bGVmdF9qb2luKGxhYmVsX21hcF9kZiwgYnkgPSBjKCJsYWJlbCIgPSAiTkJBdGxhc19sYWJlbCIpKSB8PgogICAgZHBseXI6OnJlbmFtZShmYW1pbHkgPSBOQkF0bGFzX2ZhbWlseSkgfD4KICAgICMjIyMjIyMjIyBPYnRhaW4gTEFCRUwgb250b2xvZ2llcwogICAgZHBseXI6OmxlZnRfam9pbihvbnRvbG9neV9zbGltX2RmLCBieSA9IGMoImxhYmVsIiA9ICJOQkF0bGFzX2xhYmVsIikpIHw+CiAgICBkcGx5cjo6cmVuYW1lKGxhYmVsX29udG9sb2d5ID0gQ0xfb250b2xvZ3lfaWQpIHw+CiAgICAjIyMjIyMjIyBPYnRhaW4gRkFNSUxZIG9udG9sb2dpZXMKICAgIGRwbHlyOjpsZWZ0X2pvaW4ob250b2xvZ3lfc2xpbV9kZiwgYnkgPSBjKCJmYW1pbHkiID0gIk5CQXRsYXNfbGFiZWwiKSkgfD4KICAgIGRwbHlyOjpyZW5hbWUoZmFtaWx5X29udG9sb2d5ID0gQ0xfb250b2xvZ3lfaWQpIHw+CiAgICAjIHJlbmFtZSBjb2x1bW5zIHRvIHN0YXJ0IHdpdGggYGFubm90X3R5cGVgCiAgICBkcGx5cjo6cmVuYW1lX3dpdGgoXCh4KSB7cGFzdGUoYW5ub3RfdHlwZSwgeCwgc2VwID0gIl8iKX0sIC1jZWxsX2lkKSAKfQpgYGAKCgojIyMgUmVhZCBhbmQgcHJlcGFyZSBpbnB1dCBkYXRhCgpSZWFkIG1lcmdlZCBTQ0Ugb2JqZWN0OgoKYGBge3J9Cm1lcmdlZF9zY2UgPC0gcmVhZFJEUyhzY2VfZmlsZSkKIyBpbW1lZGlhdGVseSByZW1vdmUgYXNzYXlzIHdlIGRvbid0IG5lZWQgZm9yIHNwYWNlCmFzc2F5KG1lcmdlZF9zY2UsICJzcGxpY2VkIikgPC0gTlVMTAphc3NheShtZXJnZWRfc2NlLCAiY291bnRzIikgPC0gTlVMTApyZWR1Y2VkRGltKG1lcmdlZF9zY2UsICJQQ0EiKSA8LSBOVUxMCmBgYAoKUmVhZCBgU2luZ2xlUmAgcmVzdWx0czoKCmBgYHtyfQpzaW5nbGVyX2RmIDwtIHNpbmdsZXJfZmlsZXMgfD4KICBwdXJycjo6bWFwKHJlYWRyOjpyZWFkX3RzdikgfD4KICBwdXJycjo6bGlzdF9yYmluZChuYW1lc190byA9ICJsaWJyYXJ5X2lkIikgfD4KICBkcGx5cjo6bXV0YXRlKAogICAgIyBhZGQgY2VsbCBpZCBjb2x1bW4gZm9yIHVuaXF1ZSByb3dzICYgam9pbmluZwogICAgY2VsbF9pZCA9IGdsdWU6OmdsdWUoIntsaWJyYXJ5X2lkfS17YmFyY29kZXN9IiksCiAgICAjIHJlY29kZSBOQSAtPiAiVW5rbm93biIgYW5kIE5FIC0+ICJOZXVyb2VuZG9jcmluZSIKICAgIHNpbmdsZXIgPSBkcGx5cjo6Y2FzZV93aGVuKAogICAgICBpcy5uYShwcnVuZWQubGFiZWxzKSB+ICJVbmtub3duIiwgCiAgICAgIHBydW5lZC5sYWJlbHMgPT0gIk5FIiB+ICJOZXVyb2VuZG9jcmluZSIsCiAgICAgIC5kZWZhdWx0ID0gcHJ1bmVkLmxhYmVscykKICApIHw+CiAgZHBseXI6OnNlbGVjdChjZWxsX2lkLCBzaW5nbGVyKQpgYGAKClJlYWQgYHNjQU5WSWAgcmVzdWx0czoKCmBgYHtyfQpzY2FudmlfZGYgPC0gcmVhZHI6OnJlYWRfdHN2KAogIGZpbGUucGF0aChzY2FudmlfZGlyLCAic2NhbnZpX3ByZWRpY3Rpb25zLnRzdiIpCikgfD4KICBkcGx5cjo6c2VsZWN0KAogICAgY2VsbF9pZCwgCiAgICBzY2FudmkgPSBzY2FudmlfcHJlZGljdGlvbiwgCiAgICBzdGFydHNfd2l0aCgicHBfIikKICApIHw+CiAgdGlkeXI6OnBpdm90X2xvbmdlcigKICAgIHN0YXJ0c193aXRoKCJwcF8iKSwgCiAgICBuYW1lc190byA9ICJwb3N0ZXJpb3JfY2VsbHR5cGUiLCAKICAgIHZhbHVlc190byA9ICJwb3N0ZXJpb3IiCiAgKSB8PgogIGRwbHlyOjptdXRhdGUocG9zdGVyaW9yX2NlbGx0eXBlID0gc3RyaW5ncjo6c3RyX3JlbW92ZShwb3N0ZXJpb3JfY2VsbHR5cGUsICJecHBfIikpIHw+CiAgZHBseXI6OmZpbHRlcihzY2FudmkgPT0gcG9zdGVyaW9yX2NlbGx0eXBlKSB8PgogICMgcmVjb2RlIHRvIFVua25vd24gaWYgYmVsb3cgdGhlIHRocmVzaG9sZCwgYW5kIE5FIC0+IE5ldXJvZW5kb2NyaW5lCiAgZHBseXI6Om11dGF0ZShzY2FudmkgPSBkcGx5cjo6Y2FzZV93aGVuKAogICAgcG9zdGVyaW9yIDwgcGFyYW1zJHNjYW52aV9wcF90aHJlc2hvbGQgfiAiVW5rbm93biIsIAogICAgc2NhbnZpID09ICJORSIgfiAiTmV1cm9lbmRvY3JpbmUiLCAKICAgIC5kZWZhdWx0ID0gc2NhbnZpKSkgfD4KICBkcGx5cjo6c2VsZWN0KGNlbGxfaWQsIHNjYW52aSkKYGBgCgpSZWFkIGFkZGl0aW9uYWwgaGVscGVyIGZpbGVzOgoKYGBge3J9CnBhbGV0dGVfZGYgPC0gcmVhZHI6OnJlYWRfdHN2KHBhbGV0dGVfZmlsZSkKY2VsbHR5cGVfcGFsIDwtIHBhbGV0dGVfZGYkaGV4X2NvbG9yCm5hbWVzKGNlbGx0eXBlX3BhbCkgPC0gcGFsZXR0ZV9kZiROQkF0bGFzX2xhYmVsCgpsYWJlbF9tYXBfZGYgPC0gcmVhZHI6OnJlYWRfdHN2KG5iYXRsYXNfbGFiZWxfbWFwX2ZpbGUpCm9udG9sb2d5X2RmIDwtIHJlYWRyOjpyZWFkX3RzdihuYmF0bGFzX29udG9sb2d5X2ZpbGUpCgpuYmF0bGFzX21hcmtlcnNfZGYgPC0gcmVhZHI6OnJlYWRfdHN2KG5iYXRsYXNfbWFya2Vyc19maWxlKQoKdmFsaWRhdGlvbl9kZiA8LSByZWFkcjo6cmVhZF90c3YodmFsaWRhdGlvbl91cmwpIHw+CiAgZHBseXI6OnJlbmFtZSgKICAgIGNvbnNlbnN1cyA9IGNvbnNlbnN1c19hbm5vdGF0aW9uLAogICAgY29uc2Vuc3VzX2ZhbWlseV9sYWJlbCA9IHZhbGlkYXRpb25fZ3JvdXBfYW5ub3RhdGlvbiwKICAgIGNvbnNlbnN1c19mYW1pbHlfb250b2xvZ3kgPSB2YWxpZGF0aW9uX2dyb3VwX29udG9sb2d5IAogICkKYGBgCgoKQ29tYmluZSBhbm5vdGF0aW9ucyBpbnRvIGEgc2luZ2xlIGRhdGEgZnJhbWU6CgpgYGB7cn0KY2VsbHR5cGVfZGYgPC0gc2N1dHRsZTo6bWFrZVBlckNlbGxERigKICBtZXJnZWRfc2NlLAogIHVzZS5jb2xkYXRhID0gYygic2FtcGxlX2lkIiwgImlzX3hlbm9ncmFmdCIsICJjb25zZW5zdXNfY2VsbHR5cGVfYW5ub3RhdGlvbiIsICJjb25zZW5zdXNfY2VsbHR5cGVfb250b2xvZ3kiKQopIHw+CiAgdGliYmxlOjpyb3duYW1lc190b19jb2x1bW4oImNlbGxfaWQiKSB8PgogIHRpZHlyOjpzZXBhcmF0ZShjZWxsX2lkLCBpbnRvID0gYygibGlicmFyeV9pZCIsICJjZWxsX2JhcmNvZGUiKSwgcmVtb3ZlID0gRkFMU0UpIHw+CiAgIyBrZWVwIFVNQVAgZm9yIGV2ZW50dWFsIHZpegogIGRwbHlyOjpyZW5hbWUoCiAgICBjb25zZW5zdXMgPSBjb25zZW5zdXNfY2VsbHR5cGVfYW5ub3RhdGlvbiwgCiAgICBjb25zZW5zdXNfb250b2xvZ3kgPSBjb25zZW5zdXNfY2VsbHR5cGVfb250b2xvZ3ksCiAgICBVTUFQMSA9IFVNQVAuMSwgCiAgICBVTUFQMiA9IFVNQVAuMgogICkgfD4KICBkcGx5cjo6bGVmdF9qb2luKHNpbmdsZXJfZGYsIGJ5ID0gImNlbGxfaWQiKSB8PgogIGRwbHlyOjpsZWZ0X2pvaW4oc2NhbnZpX2RmLCBieSA9ICJjZWxsX2lkIikKCgojIFB1bGwgb3V0IG1ldGFkYXRhIHdlJ2xsIHdhbnQgbGF0ZXIKc2FtcGxlX21ldGFkYXRhIDwtIGNlbGx0eXBlX2RmIHw+CiAgZHBseXI6OnNlbGVjdChzYW1wbGVfaWQsIGxpYnJhcnlfaWQsIGlzX3hlbm9ncmFmdCkgfD4KICB1bmlxdWUoKQpgYGAKCgojIyBBbm5vdGF0ZQoKRmlydCB3ZSBwcmVwYXJlIHRoZSBkYXRhIGZyYW1lIGZvciBhbm5vdGF0aW9uLgoKYGBge3IsIHdhcm5pbmcgPSBGQUxTRX0Kb250b2xvZ3lfc2xpbV9kZiA8LSBvbnRvbG9neV9kZiB8PgogIGRwbHlyOjpzZWxlY3QoLUNMX2Fubm90YXRpb24pCgpzaW5nbGVyX2Fubm90YXRpb25fZGYgPC0gcHJlcF9mb3JfYW5ub3RhdGlvbigKICBjZWxsdHlwZV9kZiwgCiAgInNpbmdsZXIiLCAKICBvbnRvbG9neV9zbGltX2RmLCAKICBsYWJlbF9tYXBfZGYKKQpzY2FudmlfYW5ub3RhdGlvbl9kZiA8LSBwcmVwX2Zvcl9hbm5vdGF0aW9uKAogIGNlbGx0eXBlX2RmLCAKICAic2NhbnZpIiwgCiAgb250b2xvZ3lfc2xpbV9kZiwgCiAgbGFiZWxfbWFwX2RmCikKCmFubm90YXRpb25fZGYgPC0gY2VsbHR5cGVfZGYgfD4KICBkcGx5cjo6c2VsZWN0KGNlbGxfaWQsIGNvbnNlbnN1cywgY29uc2Vuc3VzX29udG9sb2d5KSB8PgogIGRwbHlyOjpsZWZ0X2pvaW4oc2luZ2xlcl9hbm5vdGF0aW9uX2RmLCBieSA9ICJjZWxsX2lkIikgfD4KICBkcGx5cjo6bGVmdF9qb2luKHNjYW52aV9hbm5vdGF0aW9uX2RmLCBieSA9ICJjZWxsX2lkIikgfD4KICBkcGx5cjo6bGVmdF9qb2luKHZhbGlkYXRpb25fZGYsIGJ5ID0gYygiY29uc2Vuc3VzIiwgImNvbnNlbnN1c19vbnRvbG9neSIpKSB8PgogIGRwbHlyOjpzZWxlY3QoY2VsbF9pZCwgc3RhcnRzX3dpdGgoImNvbnNlbnN1cyIpLCBzdGFydHNfd2l0aCgic2luZ2xlciIpLCBzdGFydHNfd2l0aCgic2NhbnZpIikpCmBgYAoKCkluIHRoZSBjb2RlIGJlbG93LCB3ZSBjb21wYXJlIG9udG9sb2d5IGlkcyB0byBkZXJpdmUgZmluYWwgYW5ub3RhdGlvbnMuCkhvd2V2ZXIsIGJhc2VkIG9uIHRob3NlIGNvbXBhcmlzb25zLCB3ZSBhc3NpZ24gYSBmaW5hbCBfbGFiZWxfLCBub3QgZmluYWwgb250b2xvZ3kgaWQsIGFuZCB0aGVuIGFkZCB0aGUgZmluYWwgb250b2xvZ3kgaWRzIGJhY2sgaW50byB0aG9zZSBhbm5vdGF0aW9ucy4KVGhpcyBpcyBiZWNhdXNlIHdlIGhhdmUgc2V2ZXJhbCBgTkFgIG9udG9sb2d5IGlkcywgYW5kIHdlIHdvdWxkIG5vdCBiZSBhYmxlIHRvIHVuYW1iaWd1b3VzbHkgbWFwIHRoZWlyIGxhYmVscyBpbiBpZiB3ZSBhc3NpZ25lZCBvbnRvbG9naWVzLgoKYGBge3J9CmZpbmFsX2Fubm90YXRpb25fZGYgPC0gYW5ub3RhdGlvbl9kZiB8PiAKICBkcGx5cjo6bXV0YXRlKAogICAgIyBGb3IgYWxsIGNvbXBhcmlzb25zLCBtYWtlIHN1cmUgd2UgYXJlbid0IGNvbXBhcmluZyBOQSB0byBOQSBhbmQgZ2V0dGluZyBUUlVFCiAgICBmaW5hbF9sYWJlbCA9IGRwbHlyOjpjYXNlX3doZW4oCiAgICAgICMjIyMjIyMjIyMjIENoZWNrIGZvciBleGFjdCBtYXRjaCBiZXR3ZWVuIFNpbmdsZVIvc2NBTlZJCiAgICAgICFpcy5uYShzY2FudmlfbGFiZWxfb250b2xvZ3kpICYgc2luZ2xlcl9sYWJlbF9vbnRvbG9neSA9PSBzY2FudmlfbGFiZWxfb250b2xvZ3kgfiBzY2FudmlfbGFiZWwsIAogICAgICAjIyMjIyMjIyMjIyBDaGVjayBmb3IgZmFtaWx5IG1hdGNoIGJldHdlZW4gU2luZ2xlUi9zY0FOVkkKICAgICAgIWlzLm5hKHNjYW52aV9mYW1pbHlfb250b2xvZ3kpICYgc2luZ2xlcl9mYW1pbHlfb250b2xvZ3kgPT0gc2NhbnZpX2ZhbWlseV9vbnRvbG9neSB+IHNjYW52aV9mYW1pbHksCiAgICAgICMjIyMjIyMjIyMgTm93IHVzZSBhZ3JlZW1lbnQgd2l0aCBjb25zZW5zdXMgdG8gYXNzaWduIGEgbGFiZWw6IGZpcnN0IGJ5IGxhYmVsLCBhbmQgdGhlbiBieSBmYW1pbHkKICAgICAgIWlzLm5hKGNvbnNlbnN1c19vbnRvbG9neSkgJiBzaW5nbGVyX2xhYmVsX29udG9sb2d5ICA9PSBjb25zZW5zdXNfb250b2xvZ3kgfiBzaW5nbGVyX2xhYmVsLAogICAgICAhaXMubmEoY29uc2Vuc3VzX29udG9sb2d5KSAmIHNjYW52aV9sYWJlbF9vbnRvbG9neSAgID09IGNvbnNlbnN1c19vbnRvbG9neSB+IHNjYW52aV9sYWJlbCwKICAgICAgIWlzLm5hKGNvbnNlbnN1c19mYW1pbHlfb250b2xvZ3kpICYgc2luZ2xlcl9mYW1pbHlfb250b2xvZ3kgPT0gY29uc2Vuc3VzX2ZhbWlseV9vbnRvbG9neSB+IHNpbmdsZXJfZmFtaWx5LAogICAgICAhaXMubmEoY29uc2Vuc3VzX2ZhbWlseV9vbnRvbG9neSkgJiBzY2FudmlfZmFtaWx5X29udG9sb2d5ICA9PSBjb25zZW5zdXNfZmFtaWx5X29udG9sb2d5IH4gc2NhbnZpX2ZhbWlseSwKICAgICAgIyBFdmVyeXRoaW5nIGVsc2UgaXMgVW5rbm93bgogICAgICAuZGVmYXVsdCA9ICJVbmtub3duIgogICAgKQogICkgfD4KICAjIE5vdywgd2UgY2FuIGJyaW5nIG1hcCB0aGUgZmluYWwgb250b2xvZ2llcyBpbnRvIHRoZSBkYXRhIGZyYW1lCiAgZHBseXI6OmlubmVyX2pvaW4oCiAgICBvbnRvbG9neV9kZiB8PiBkcGx5cjo6cmVuYW1lKGZpbmFsX2xhYmVsID0gTkJBdGxhc19sYWJlbCwgZmluYWxfaWQgPSBDTF9vbnRvbG9neV9pZCksIAogICAgYnkgPSAiZmluYWxfbGFiZWwiCiAgKSB8PgogICMgYWRkIHR1bW9yIGNvbHVtbiBmb3IgdmlzdWFsaXphdGlvbgogIGRwbHlyOjptdXRhdGUoY2VsbF9jbGFzcyA9IGRwbHlyOjpjYXNlX3doZW4oCiAgICBmaW5hbF9sYWJlbCA9PSAiTmV1cm9lbmRvY3JpbmUiIH4gInR1bW9yIiwgCiAgICBmaW5hbF9sYWJlbCA9PSAiVW5rbm93biIgfiAidW5rbm93biIsIAogICAgLmRlZmF1bHQgPSAibm9ybWFsIgogICkpCmBgYCAKCgojIyBFeHBsb3JlIGZpbmFsIGFubm90YXRpb25zCgpXaGF0IGZyYWN0aW9uIG9mIGNlbGxzIGhhdmUgYmVlbiBhbm5vdGF0ZWQ/CgoKYGBge3J9CnN1bSghaXMubmEoZmluYWxfYW5ub3RhdGlvbl9kZiRmaW5hbF9pZCkpIC8gbnJvdyhmaW5hbF9hbm5vdGF0aW9uX2RmKQpgYGAKCgpXaGF0IGZyYWN0aW9uIG9mIGNlbGxzIGhhdmUgYmVlbiBhbm5vdGF0ZWQgcGVyIGxpYnJhcnk/CgpgYGB7ciwgZmlnLndpZHRoID0gOCwgZmlnLmhlaWdodCA9IDR9CmZyYWNfbGFiZWxlZF9kZiA8LSBmaW5hbF9hbm5vdGF0aW9uX2RmIHw+CiAgdGlkeXI6OnNlcGFyYXRlKGNlbGxfaWQsIGludG8gPSBjKCJsaWJyYXJ5X2lkIiwgImJhcmNvZGUiKSwgcmVtb3ZlID0gRkFMU0UpIHw+CiAgZHBseXI6Omdyb3VwX2J5KGxpYnJhcnlfaWQpIHw+CiAgZHBseXI6OnN1bW1hcml6ZSgKICAgIGZyYWNfbGFiZWxlZCA9IHN1bSghaXMubmEoZmluYWxfaWQpKS9kcGx5cjo6bigpLCAKICAgIGxpYnJhcnlfc2l6ZSA9IGRwbHlyOjpuKCkKICApIHw+CiAgIyBnZXQgUERYIGxvZ2ljYWwgZm9yIGNvbG9yaW5nCiAgZHBseXI6OmlubmVyX2pvaW4oc2FtcGxlX21ldGFkYXRhLCBieSA9ICJsaWJyYXJ5X2lkIikKCgpwMSA8LSBnZ3Bsb3QoZnJhY19sYWJlbGVkX2RmKSArIAogIGFlcyh4ID0gZnJhY19sYWJlbGVkKSArIAogIGdlb21faGlzdG9ncmFtKGJpbnMgPSA0MCkgKwogIGxhYnMoeCA9ICJGcmFjdGlvbiBvZiBsaWJyYXJ5IGxhYmVsZWQiKSArCiAgdGhlbWVfYncoKQoKCnAyIDwtIGdncGxvdChmcmFjX2xhYmVsZWRfZGYpICsgCiAgYWVzKHggPSBsaWJyYXJ5X3NpemUsIHkgPSBmcmFjX2xhYmVsZWQsIGNvbG9yID0gaXNfeGVub2dyYWZ0KSArIAogIGdlb21fcG9pbnQoKSArIAogIGxhYnMoCiAgICB4ID0gIlRvdGFsIGNlbGxzIGluIGxpYnJhcnkiLAogICAgeSA9ICJGcmFjdGlvbiBvZiBsaWJyYXJ5IGxhYmVsZWQiCiAgKSArCiAgdGhlbWVfYncoKSArCiAgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gImJvdHRvbSIpCgoKcDEgKyBwMgpgYGAKCiMjIyBVTUFQcwoKSW4gdGhpcyBzZWN0aW9uLCB3ZSBzaG93IHRoZSBVTUFQIGZyb20gdGhlIG1lcmdlZCAobm90IGJhdGNoLWNvcnJlY3RlZCEpIGBTQ1BDUDAwMDAwNGAgb2JqZWN0IGNvbG9yZWQgYnkgY2VsbCB0eXBlIGFubm90YXRpb25zIGFuZCBvdGhlciBwb3RlbnRpYWxseSByZWxldmFudCBtZXRhZGF0YS4KCgpgYGB7cn0KbGFiZWxfb3JkZXIgPC0gbGFiZWxfbWFwX2RmIHw+CiAgZHBseXI6OmFkZF9jb3VudChOQkF0bGFzX2ZhbWlseSkgfD4KICBkcGx5cjo6YXJyYW5nZShkZXNjKG4pLCBOQkF0bGFzX2ZhbWlseSkgfD4KICBkcGx5cjo6ZmlsdGVyKE5CQXRsYXNfbGFiZWwgJWluJSBmaW5hbF9hbm5vdGF0aW9uX2RmJGZpbmFsX2xhYmVsKSB8PgogIGRwbHlyOjpwdWxsKE5CQXRsYXNfbGFiZWwpCmxhYmVsX29yZGVyIDwtIGMobGFiZWxfb3JkZXIsICJVbmtub3duIikKCiMgcHJlcGFyZSBkYXRhIGZyYW1lIGZvciBwbG90dGluZwp1bWFwX2RmIDwtIGNlbGx0eXBlX2RmIHw+CiAgZHBseXI6OnNlbGVjdChjZWxsX2lkLCBzYW1wbGVfaWQsIGxpYnJhcnlfaWQsIGlzX3hlbm9ncmFmdCwgVU1BUDEsIFVNQVAyKSB8PgogIGRwbHlyOjpsZWZ0X2pvaW4oZmluYWxfYW5ub3RhdGlvbl9kZiwgYnkgPSAiY2VsbF9pZCIpIHw+CiAgZHBseXI6Om11dGF0ZShmaW5hbF9sYWJlbCA9IGZhY3RvcihmaW5hbF9sYWJlbCwgbGV2ZWxzID0gbGFiZWxfb3JkZXIpKQpgYGAKCgojIyMjIENvbG9yZWQgYnkgY2VsbCB0eXBlIGFzc2lnbm1lbnQKCk5vdGUgdGhhdCBpbiB0aGUgVU1BUCBiZWxvdywgY2VsbCB0eXBlcyBpbiB0aGVzZSBzYW1lICJmYW1pbHkiIGVhY2ggc2hhcmUgYSBjb2xvcjoKCiogVCBjZWxscwoqIE5hdHVyYWwga2lsbGVyIGNlbGxzCiogQ29udmVudGlvbmFsIGRlbmRyaXRpYyBjZWxscwoqIE1vbm9jeXRlcwoKYGBge3IgZmlnLndpZHRoID0gMTAsIGZpZy5oZWlnaHQgPSAxMH0KZ2dwbG90KHVtYXBfZGYpICsKICBhZXMoeCA9IFVNQVAxLCB5ID0gVU1BUDIsIGNvbG9yID0gZmluYWxfbGFiZWwpICsKICBnZW9tX3BvaW50KGFscGhhID0gMC4yLCBzaXplID0gMC4zKSArCiAgc2NhbGVfY29sb3JfbWFudWFsKHZhbHVlcyA9IGNlbGx0eXBlX3BhbCkgKwogIGd1aWRlcyhjb2xvciA9IGd1aWRlX2xlZ2VuZChvdmVycmlkZS5hZXMgPSBsaXN0KHNpemUgPSAyLCBhbHBoYSA9IDEpKSkgKyAKICB1bWFwX3RoZW1lICsKICB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAiYm90dG9tIikKYGBgCgojIyMjIEZhY2V0ZWQgYnkgY2VsbCB0eXBlIGFzc2lnbm1lbnQKClRoaXMgVU1BUCBpcyB0aGUgc2FtZSBhcyB0aGUgcHJldmlvdXMgb25lLCBleGNlcHQgZWFjaCBmYWNldCBoaWdobGlnaHRzIG9uZSBzcGVjaWZpYyBjZWxsIHR5cGUuCkluIHRoaXMgcGxvdCwgb25seSBjZWxsIHR5cGVzIHdpdGggYXQgbGVhc3QgMTAgY2VsbHMgYXJlIHNob3duLgoKCmBgYHtyIGZpZy53aWR0aCA9IDE2LCBmaWcuaGVpZ2h0ID0gMTZ9CnVtYXBfZGYgfD4KICBkcGx5cjo6YWRkX2NvdW50KGZpbmFsX2xhYmVsKSB8PgogIGRwbHlyOjpmaWx0ZXIobiA+IDEwKSB8PgogIGZhY2V0ZWRfdW1hcCgKICAgIGFubm90YXRpb25fY29sdW1uID0gZmluYWxfbGFiZWwsCiAgICBjZWxsdHlwZV9jb2xvcnMgPSBjZWxsdHlwZV9wYWwsIAogICAgZmFjZXRfd3JhcF9jb2xzID0gNgogICkgKyAKICAgIHVtYXBfdGhlbWUgKwogICAgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gIm5vbmUiKQpgYGAKCgojIyMjIENvbG9yZWQgYnkgdHVtb3IgY2xhc3NpZmljYXRpb24KCgpgYGB7ciBmaWcud2lkdGggPSA4LCBmaWcuaGVpZ2h0ID0gOH0KZ2dwbG90KHVtYXBfZGYpICsKICBhZXMoeCA9IFVNQVAxLCB5ID0gVU1BUDIsIGNvbG9yID0gY2VsbF9jbGFzcykgKwogIGdlb21fcG9pbnQoYWxwaGEgPSAwLjIsIHNpemUgPSAwLjMpICsKICBzY2FsZV9jb2xvcl9icmV3ZXIocGFsZXR0ZSA9ICJTZXQyIikgKwogIGd1aWRlcyhjb2xvciA9IGd1aWRlX2xlZ2VuZChvdmVycmlkZS5hZXMgPSBsaXN0KHNpemUgPSAyLCBhbHBoYSA9IDEpKSkgKwogIHVtYXBfdGhlbWUKYGBgCgojIyMjIENvbG9yZWQgYnkgUERYIHN0YXR1cwoKYGBge3IgZmlnLndpZHRoID0gOCwgZmlnLmhlaWdodCA9IDh9CmdncGxvdCh1bWFwX2RmKSArCiAgYWVzKHggPSBVTUFQMSwgeSA9IFVNQVAyLCBjb2xvciA9IGlzX3hlbm9ncmFmdCkgKwogIGdlb21fcG9pbnQoYWxwaGEgPSAwLjIsIHNpemUgPSAwLjMpICsKICBzY2FsZV9jb2xvcl9icmV3ZXIocGFsZXR0ZSA9ICJTZXQxIikgKwogIGd1aWRlcyhjb2xvciA9IGd1aWRlX2xlZ2VuZChvdmVycmlkZS5hZXMgPSBsaXN0KHNpemUgPSAyLCBhbHBoYSA9IDEpKSkgKwogIHVtYXBfdGhlbWUKYGBgCgoKIyMjIERvdCBwbG90CgpUaGlzIHNlY3Rpb24gc2hvd3MgZG90IHBsb3RzIG9mIG1hcmtlciBnZW5lIGV4cHJlc3Npb24gZm9yIHRoZSBmaW5hbCBhbm5vdGF0aW9ucy4KU3BlY2lmaWNhbGx5LCB3ZSBzaG93IHRoZSB0b3AgNSBoaWdoZXN0IGBsb2cyRkNgIG1hcmtlciBnZW5lcyBwZXIgTkJBdGxhcyBjZWxsIHR5cGUgZm9yIHZhbGlkYXRpb24uCk1hcmtlciBnZW5lcyBhcmUgdGFrZW4gZGlyZWN0bHkgZnJvbSB0aGUgTkJBdGxhcyBwYXBlciB3aGVyZSB0aGV5IHdlcmUgaWRlbnRpZmllZCB3aXRoIGBTZXVyYXQ6OkZpbmRNYXJrZXJzKClgLiAKCk1hcmtlciBnZW5lcyB0aGF0IGFyZSBwcmVzZW50IGluIHRocmVlIG9yIG1vcmUgY2VsbCB0eXBlcyBhcmUgbm90IGluY2x1ZGVkLgpJbiBhZGRpdGlvbiwgdGhlIHVwcGVyIGxpbWl0IGZvciB0aGUgY29sb3Igc2NhbGUgaGFzIGJlZW4gY2Vuc29yZWQgdG8gYSBtYXhpbXVtIG9mIDYgdG8gZW5zdXJlIHZpc2liaWxpdHkgb2YgZXhwcmVzc2lvbiBsZXZlbHMgYWNyb3NzIGFsbCBjZWxsIHR5cGVzLgoKYGBge3J9CmlucHV0X2RvdHBsb3RfZGYgPC0gZmluYWxfYW5ub3RhdGlvbl9kZiB8PgogIGRwbHlyOjpmaWx0ZXIoZmluYWxfbGFiZWwgIT0gIlVua25vd24iKSB8PgogICMgcmVjb2RlIGNlbGwgdHlwZXMgdG8gbWF0Y2ggd2hhdCBpcyBwcmVzZW50IGluIE5CQXRsYXMgbWFya2VyIGdlbmVzCiAgZHBseXI6Om11dGF0ZSgKICAgIGxhYmVsX3JlY29kZWQgPSBkcGx5cjo6Y2FzZV93aGVuKAogICAgICBzdHJpbmdyOjpzdHJfZGV0ZWN0KGZpbmFsX2xhYmVsLCAibW9ub2N5dGUiKSB+ICJNb25vY3l0ZSIsIAogICAgICBzdHJpbmdyOjpzdHJfZGV0ZWN0KGZpbmFsX2xhYmVsLCAiY0RDIikgfiAiY0RDIiwKICAgICAgLmRlZmF1bHQgPSBmaW5hbF9sYWJlbAogICkpIHw+CiAgZHBseXI6OnNlbGVjdChjZWxsX2lkLCBsYWJlbF9yZWNvZGVkKQoKIyBkYXRhIGZyYW1lIG9mIHRvcCA1IHVwcmVndWxhdGVkIGdlbmVzIHBlciBjZWxsIHR5cGUKdG9wX25iYXRsYXNfbWFya2Vyc19kZiA8LSBzdWJzZXRfbmJhdGxhc19tYXJrZXJzKG5iYXRsYXNfbWFya2Vyc19kZiwgNSwgInVwIikgfD4KICAjIG9ubHkgbWFya2VycyBpbiBubyBtb3JlIHRoYW4gMiBjYXRlZ29yaWVzCiAgIyB0aGlzIGlzIGEgZ29vZCBtaWRkbGUgZ3JvdW5kIHRvIGVuc3VyZSB0aGVyZSBhcmUgYXQgbGVhc3QgMiBtYXJrZXJzIHBlciBjZWxsIHR5cGUKICBkcGx5cjo6YWRkX2NvdW50KGVuc2VtYmxfZ2VuZV9pZCkgfD4KICBkcGx5cjo6ZmlsdGVyKG4gPCAzKSB8PgogIGRwbHlyOjpzZWxlY3QoLW4pIApgYGAKCgpgYGB7cn0KIyBnZXQgdG90YWwgbnVtYmVyIG9mIGNlbGxzIHBlciBmaW5hbCBhbm5vdGF0aW9uIGdyb3VwIGFuZCBzZXQgdXAgeV9sYWJlbAp0b3RhbF9jZWxsc19kZiA8LSBpbnB1dF9kb3RwbG90X2RmIHw+CiAgZHBseXI6OmNvdW50KGxhYmVsX3JlY29kZWQsIG5hbWUgPSAidG90YWxfY2VsbHMiKSB8PgogIGRwbHlyOjptdXRhdGUoeV9sYWJlbCA9IGdsdWU6OmdsdWUoIntsYWJlbF9yZWNvZGVkfSAoe3RvdGFsX2NlbGxzfSkiKSkgfD4KICBkcGx5cjo6aW5uZXJfam9pbihsYWJlbF9tYXBfZGYsIGJ5ID0gYygibGFiZWxfcmVjb2RlZCIgPSAiTkJBdGxhc19sYWJlbCIpKSB8PgogIGRwbHlyOjpncm91cF9ieShOQkF0bGFzX2ZhbWlseSkgfD4KICBkcGx5cjo6bXV0YXRlKGZhbWlseV90b3RhbF9jZWxscyA9IHN1bSh0b3RhbF9jZWxscykpIHw+CiAgIyBvcmRlciBieSBmYW1pbHkgY291bnQsIGJ1dCBwdXQgVW5rbm93biBhdCB0aGUgZW5kCiAgZHBseXI6OmFycmFuZ2UoZGVzYyhmYW1pbHlfdG90YWxfY2VsbHMpKSB8PgogICMgYW1hemluZyBuZXcgZHBseXIgdHJpY2s7IG9ubHkgdGhpcyByb3cgaXMgVFJVRSwgc28gaXQgZ2V0cyBtb3ZlZCB0byB0aGUgbGFzdCByb3cKICBkcGx5cjo6YXJyYW5nZShsYWJlbF9yZWNvZGVkID09ICJVbmtub3duIikKCnRvdGFsX2NlbGxzX2RmJHlfbGFiZWwgPC0gZmFjdG9yKHRvdGFsX2NlbGxzX2RmJHlfbGFiZWwsIGxldmVscyA9IHJldih0b3RhbF9jZWxsc19kZiR5X2xhYmVsKSkKbmJhdGxhc19iYXJfb3JkZXIgPC0gdG90YWxfY2VsbHNfZGYkbGFiZWxfcmVjb2RlZApgYGAKCmBgYHtyfQojIFByZXBhcmUgdGhlIGV4cHJlc3NlZF9nZW5lcyB2ZWN0b3IKIyB3ZSBvbmx5IGNhcmUgYWJvdXQgaWYgdGhhdCBnZW5lIGlzIGV4cHJlc3NlZCBvdGhlcndpc2Ugd2Ugd29uJ3Qgd2FzdGUgbWVtb3J5IGFuZCBpbmNsdWRlIGl0CmdlbmVfc3VtcyA8LSByb3dEYXRhKG1lcmdlZF9zY2UpIHw+CiAgYXMuZGF0YS5mcmFtZSgpIHw+CiAgZHBseXI6OnNlbGVjdChjb250YWlucygiZGV0ZWN0ZWQiKSkgfD4KICBhcy5tYXRyaXgoKSB8PgogIHJvd1N1bXMoKQpleHByZXNzZWRfZ2VuZXMgPC0gbmFtZXMoZ2VuZV9zdW1zKVtnZW5lX3N1bXMgPiAwXQpgYGAKCmBgYHtyLCBmaWcud2lkdGggPSAzMiwgZmlnLmhlaWdodCA9IDE2LCBldmFsID0gIXBhcmFtcyR0ZXN0aW5nfQpnZW5lcmF0ZV9kb3RwbG90KAogIG1lcmdlZF9zY2UgPSBtZXJnZWRfc2NlLAogIG1hcmtlcnNfZGYgPSB0b3BfbmJhdGxhc19tYXJrZXJzX2RmLAogIGNlbGx0eXBlX2RmID0gaW5wdXRfZG90cGxvdF9kZiwKICB0b3RhbF9jZWxsc19kZiA9IHRvdGFsX2NlbGxzX2RmLAogIGV4cHJlc3NlZF9nZW5lcyA9IGV4cHJlc3NlZF9nZW5lcywKICBiYXJfb3JkZXIgPSBuYmF0bGFzX2Jhcl9vcmRlciwgCiAgY2VsbHR5cGVfcGFsZXR0ZSA9IGNlbGx0eXBlX3BhbCwgCiAgbWluX2NlbGxzID0gMTAgIyBvbmx5IHNob3cgY2VsbCB0eXBlcyB3aXRoIGF0IGxlYXN0IDEwIGNlbGxzCikgKwogIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJib3R0b20iKQpgYGAKCgojIyBFeHBvcnQKCkV4cG9ydCB0aGUgZmluYWwgdGFibGUgd2l0aCBzdWJtaXNzaW9uLXJlYWR5IGNvbHVtbiBuYW1lcy4KCmBgYHtyfQpmaW5hbF9hbm5vdGF0aW9uX2RmIHw+CiAgZHBseXI6OnNlbGVjdCgKICAgIGNlbGxfaWQsIAogICAgY2VsbF90eXBlX2Fzc2lnbm1lbnQgPSBmaW5hbF9sYWJlbCwgCiAgICB0dW1vcl9jZWxsX2NsYXNzaWZpY2F0aW9uID0gY2VsbF9jbGFzcywKICAgIENMX29udG9sb2d5X2lkID0gZmluYWxfaWQsIAogICAgQ0xfYW5ub3RhdGlvbgogICkgfD4KICByZWFkcjo6d3JpdGVfdHN2KAogICAgcGFyYW1zJGFubm90YXRpb25zX3RzdgogICkKYGBgCgojIyBTZXNzaW9uIEluZm8KCmBgYHtyfQpzZXNzaW9uSW5mbygpCmBgYAo=